package com.group.itms.routes.activities;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.WeakHashMap;

import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v4.app.ActionBarDrawerToggle;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;

import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuItem;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.OnInfoWindowClickListener;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.GroundOverlayOptions;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.maps.model.PolygonOptions;
import com.google.android.gms.maps.model.PolylineOptions;
import com.group.itms.routes.R;
import com.group.itms.routes.data.BObject;
import com.group.itms.routes.data.ObjectGps;
import com.group.itms.routes.data.ObjectMap;
import com.group.itms.routes.data.ObjectType;
import com.group.itms.routes.map.Map.MapType;

public class NewMapActivity extends BaseActivity {

	private static final String FROM_POINT = "from_point";
	private static final String MAP_IMAGE_VISIBILITY = "map_image_visibility";
	private static final String CHECKED_OBJECT_TYPES = "checked_object_types";
	private static final String MAP_TYPE = "map_type";
	
	public static final String TAG = "MapActivity";

	private static final int OBJECT_TYPE_ROUTE = 0;
	private static final int OBJECT_TYPE_TASK = 1;
	private static final int OBJECT_TYPE_POINT = 2;
	private static final int OBJECT_TYPE_LOCATION = 3;

	private BObject mObject;
	private GoogleMap mMap;
	private GroundOverlayOptions mMapImage;

	private List<BObject> mObjectsTree;
	private boolean[] mVisibleObjectTypes = new boolean[] { true, true, true, true };
	private boolean mRestoring;
	private HashMap<Marker, BObject> mMarkerData = new HashMap<Marker, BObject>();

	private WeakHashMap<BObject, List<ObjectGps>> mWeakMap = new WeakHashMap<BObject, List<ObjectGps>>();
	
	private DrawerLayout mDrawerLayout;
	private ListView mLayerDrawerListView;
	private ActionBarDrawerToggle mDrawerToggle;
	private boolean mPendingMapImageVisibility;
	
	private boolean mFromPoint;
	
	private boolean mMapSetup;
	private Menu mMenu;
	private int mCurrentMapType;
	
	public static Intent getIntent(Context ctx, int objId) {
		return getIntent(ctx, objId, false);
	}
	
	public static Intent getIntent(Context ctx, int objId, boolean fromPoint) {
		Intent i = new Intent(ctx, NewMapActivity.class);
		i.putExtra(OBJECT_ID, objId);
		i.putExtra(FROM_POINT, fromPoint);
		return i;
	}

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		mDrawerLayout = (DrawerLayout) getLayoutInflater().inflate(R.layout.activity_map_with_drawer, null);
		setContentView(mDrawerLayout);
		if (savedInstanceState == null) {
			getSupportFragmentManager()
			.beginTransaction()
			.add(R.id.content, SupportMapFragment.newInstance())
			.commit();
			mCurrentMapType = MapType.Stallite.ordinal();
		} else {
			mVisibleObjectTypes = savedInstanceState.getBooleanArray(CHECKED_OBJECT_TYPES);
			mPendingMapImageVisibility = savedInstanceState.getBoolean(MAP_IMAGE_VISIBILITY);
			mCurrentMapType = savedInstanceState.getInt(MAP_TYPE);
			mRestoring = true;
		}
		int objId = getIntent().getIntExtra(OBJECT_ID, -1);
		mFromPoint = getIntent().getBooleanExtra(FROM_POINT, false);
		mObject = getObject(objId);
		getSupportActionBar().setTitle(mObject.getName());
		setUpDrawer();
		setUpMapIfndeeded();
	}

	private void setUpDrawer() {
		ArrayAdapter<String> layerDrawerAdapter = new ArrayAdapter<String>(
				this, android.R.layout.simple_list_item_multiple_choice,
				Arrays.asList("Routes", "Tasks", "Points", "Location"));

		mLayerDrawerListView = (ListView) findViewById(R.id.layer_drawer);
		mLayerDrawerListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
		mLayerDrawerListView.setAdapter(layerDrawerAdapter);
		if (!mRestoring) {
			mLayerDrawerListView.setItemChecked(0, true);
			mLayerDrawerListView.setItemChecked(1, true);
			mLayerDrawerListView.setItemChecked(2, true);
			mLayerDrawerListView.setItemChecked(3, true);
		}
		mLayerDrawerListView.setOnItemClickListener(new OnItemClickListener() {

			@Override
			public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
				boolean checked = mLayerDrawerListView.isItemChecked(position);
				mLayerDrawerListView.setItemChecked(position, checked);
				mVisibleObjectTypes[position] = checked;
				updateMap();
			}
		});
		mDrawerToggle = new ActionBarDrawerToggle(
				this, 
				mDrawerLayout, 
				R.drawable.ic_drawer, 
				R.string.open_drawer, 
				R.string.closed_drawer) {
			
			@Override
			public void onDrawerClosed(View drawerView) {
				super.onDrawerClosed(drawerView);
				getSupportActionBar().setTitle(mObject.getName());
			}
			
			@Override
			public void onDrawerOpened(View drawerView) {
				super.onDrawerOpened(drawerView);
				getSupportActionBar().setTitle("Choose objects to show");
			}
		};
		mDrawerLayout.setDrawerListener(mDrawerToggle);
	}
	
	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getSupportMenuInflater().inflate(R.menu.menu_map, menu);
		mMenu = menu;
		initMapImageVisibility();
		return super.onCreateOptionsMenu(menu);
	}

	private void initMapImageVisibility() {
		boolean visible = mRestoring ? mPendingMapImageVisibility : !hasConnection();
		setMapImageVisibility(visible, mMapSetup);
	}

	@Override
	public void onConfigurationChanged(Configuration newConfig) {
		super.onConfigurationChanged(newConfig);
		mDrawerToggle.onConfigurationChanged(newConfig);
	}
	
	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		if (item != null && item.getItemId() == android.R.id.home && mDrawerToggle.isDrawerIndicatorEnabled()) {
            if (mDrawerLayout.isDrawerVisible(GravityCompat.START)) {
                mDrawerLayout.closeDrawer(GravityCompat.START);
            } else {
                mDrawerLayout.openDrawer(GravityCompat.START);
            }
            return true;
        }
		switch (item.getItemId()) {
		case R.id.Hybrid:
			mMap.setMapType(GoogleMap.MAP_TYPE_HYBRID);
			mCurrentMapType = MapType.Hybrid.ordinal();
			break;
		case R.id.Normal:
			mMap.setMapType(GoogleMap.MAP_TYPE_NORMAL);
			mCurrentMapType = MapType.Normal.ordinal();
			break;
		case R.id.Satellite:
			mMap.setMapType(GoogleMap.MAP_TYPE_SATELLITE);
			mCurrentMapType = MapType.Stallite.ordinal();
			break;
		case R.id.Terrain:
			mMap.setMapType(GoogleMap.MAP_TYPE_TERRAIN);
			mCurrentMapType = MapType.Terrain.ordinal();
			break;
		case R.id.go_to_pois:
        	moveCameraToPOI();
        	return true;
		case R.id.toggle_show_map_image:
			if (isMapImageVisible()) {
				setMapImageVisibility(false);
			} else {
				setMapImageVisibility(true);
			}
			return true;
        }
        return super.onOptionsItemSelected(item);
	}
	
	@Override
	protected void onPostCreate(Bundle savedInstanceState) {
		super.onPostCreate(savedInstanceState);
		mDrawerToggle.syncState();
	}

	@Override
	protected void onResume() {
		super.onResume();
		setUpMapIfndeeded();
	}

	private void setUpMapIfndeeded() {
		if (mMap == null) {
			mMap = getGoogleMap();
			if (mMap != null) {
				setUpMap();
			}
		}
	}

	private void setUpMap() {
		ObjectMap m = findOfflineMap(mObject);
		LatLng southwest = new LatLng(m.getY2(), m.getX2());
		LatLng northeast = new LatLng(m.getY4(), m.getX4());

		byte[] map = m.getMap();
		Bitmap bmp = BitmapFactory.decodeByteArray(map, 0, map.length);
		mMapImage = new GroundOverlayOptions().image(
				BitmapDescriptorFactory.fromBitmap(bmp)).positionFromBounds(
				new LatLngBounds(southwest, northeast));
		mMap.setMyLocationEnabled(true);
		mMap.setMapType(mCurrentMapType);
		mMap.setOnInfoWindowClickListener(new OnInfoWindowClickListener() {

			@Override
			public void onInfoWindowClick(Marker marker) {
				BObject obj = mMarkerData.get(marker);
				if (obj.isPoint()) {
					if (!mFromPoint) {
						Intent i = InformationPointActivity.getIntent(NewMapActivity.this, obj.getId(), true);
						startActivity(i);
					}
				} else if(obj.isTask()) {
					new AlertDialog.Builder(NewMapActivity.this)
					.setTitle(marker.getTitle())
					.setMessage(marker.getSnippet())
					.setPositiveButton("Close", null)
					.show();
				}
			}
		});
		traverseObjects(mObject);
		updateMap(!mRestoring);
		mMapSetup = true;
		initMapImageVisibility();
	}
	
	private void setMapImageVisibility(boolean visible) {
		setMapImageVisibility(visible, true);
	}
	
	private void setMapImageVisibility(boolean visible, boolean update) {
		if (mMapImage == null) {
			return;
		}
		MenuItem item = mMenu != null ? mMenu.findItem(R.id.toggle_show_map_image) : null;
		if (visible) {
			mMapImage.transparency(0);
			if (item != null) {
				item.setTitle("Hide map image");
			}
		} else {
			mMapImage.transparency(1);
			if (item != null) {
				item.setTitle("Show map image");
			}
		}
		if (update) {
			updateMap();
		}
	}

	private boolean isMapImageVisible() {
		return mMapImage != null ? mMapImage.getTransparency() == 0 : false;
	}
	
	private GoogleMap getGoogleMap() {
		SupportMapFragment mf = findMapFragment();
		if (mf == null) {
			return null;
		}
		return mf.getMap();
	}

	private SupportMapFragment findMapFragment() {
		return (SupportMapFragment) getSupportFragmentManager()
				.findFragmentById(R.id.content);
	}

	private ObjectMap findOfflineMap(BObject object) {
		if (object == null) {
			return null;
		}
		if (object.is(ObjectType.LOCATION)) {
			List<ObjectMap> omList;
			try {
				omList = getHelper().getObjectMapDao().queryForEq(
						ObjectMap.OBJECT, object);
			} catch (SQLException e) {
				throw new RuntimeException(e);
			}
			if (omList.size() > 0) {
				return omList.get(0);
			}
		}
		return findOfflineMap(object.getParent());
	}

	private void traverseObjects(BObject object) {
		if (object == null) {
			return;
		}
		if (mObjectsTree == null) {
			mObjectsTree = new ArrayList<BObject>();
			mObjectsTree.add(mObject);
		}
		List<BObject> children = object.getChildren();
		mObjectsTree.addAll(children);
		for (BObject ch : children) {
			traverseObjects(ch);
		}
	}

	private void updateMap() {
		updateMap(false);
	}
	
	private void updateMap(boolean initializing) {
		if (mMap == null) {
			return;
		}
		mMarkerData.clear();
		mMap.clear();
		mMap.addGroundOverlay(mMapImage);
		for (BObject o : mObjectsTree) {
			if (isObjectVisible(o)) {
				if (o.isLocation()) {
					drawPolygon(getGpsObjects(o), Color.RED);
				} else if (o.isRoute()) {
					drawPolyline(getGpsObjects(o), toIntColor(o.getColor()));
				} else if (o.isTask()) {
					Marker m = drawMarker(findFirstGpsObject(o), R.drawable.ic_task, o.getName(), o.getText());
					mMarkerData.put(m, o);
				} else { // point
					Marker m = drawMarker(findFirstGpsObject(o), R.drawable.ic_information, o.getName(), "information point");
					mMarkerData.put(m, o);
				}
			}
		}
		if (initializing) {
			moveCameraToPOI();
		}
	}

	private void moveCameraToPOI() {
		LatLngBounds bounds = mMapImage.getBounds();
		LatLng ne = bounds.northeast;
		LatLng sw = bounds.southwest;
		LatLng latLng = new LatLng(0.5 * (ne.latitude + sw.latitude), 0.5 * (ne.longitude + sw.longitude));
		mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, 12));
	}

	private void drawPolygon(List<ObjectGps> pts, int color) {
		PolygonOptions pol = new PolygonOptions();
		for (ObjectGps p : pts) {
			pol.add(toLatLng(p));
		}
		pol.strokeColor(color);
		pol.strokeWidth(5);
		mMap.addPolygon(pol);
	}

	private void drawPolyline(List<ObjectGps> pts, int color) {
		PolylineOptions pol = new PolylineOptions();
		ObjectGps first = null;
		int n = pts.size();
		for (int i = 0; i < n; i++) {
			ObjectGps gpsPt = pts.get(i);
			if (i == 0) {
				first = gpsPt;
			}
			pol.add(toLatLng(gpsPt));	
		}
		pol.color(color);
		pol.width(7);
		mMap.addPolyline(pol);
		if (first != null) {
			Bitmap pBmp = BitmapFactory.decodeResource(getResources(), R.drawable.ic_point_map);
			LatLng ll = toLatLng(first);

//			Point p = mMap.getProjection().toScreenLocation(ll);
//			p.x -= pBmp.getWidth() / 2;
//			p.y -= pBmp.getHeight() / 2;
			
			mMap.addMarker(new MarkerOptions()
			//.position(mMap.getProjection().fromScreenLocation(p))
			.position(ll)
			.icon(BitmapDescriptorFactory.fromBitmap(pBmp)));
		}
	}

	private Marker drawMarker(ObjectGps o, int icId, String title, String snippet) {
		Marker m = mMap.addMarker(new MarkerOptions()
		.position(toLatLng(o))
		.icon(BitmapDescriptorFactory.fromResource(icId)));
		m.setTitle(title);
		m.setSnippet(snippet);
		return m;
	}

	private boolean isObjectVisible(BObject object) {
		int ind;
		if (object.isLocation()) {
			ind = OBJECT_TYPE_LOCATION;
		} else if (object.isRoute()) {
			ind = OBJECT_TYPE_ROUTE;
		} else if (object.isTask()) {
			ind = OBJECT_TYPE_TASK;
		} else { // point
			ind = OBJECT_TYPE_POINT;
		}
		return mVisibleObjectTypes[ind];
	}

	private ObjectGps findFirstGpsObject(BObject object) {
		List<ObjectGps> ogList = object.getGpsObjects();
		if (ogList.size() > 0) {
			return ogList.get(0);
		}
		return null;
	}

	private LatLng toLatLng(ObjectGps o) {
		return new LatLng(o.getY(), o.getX());
	}

	@Override
	protected String getTag() {
		return TAG;
	}

	@Override
	protected void onSaveInstanceState(Bundle outState) {
		super.onSaveInstanceState(outState);
		outState.putBooleanArray(CHECKED_OBJECT_TYPES, mVisibleObjectTypes);
		outState.putBoolean(MAP_IMAGE_VISIBILITY, isMapImageVisible());
		outState.putInt(MAP_TYPE, mCurrentMapType);
	}

	private int toIntColor(String sCol) {
		try {
			return Long.decode(sCol).intValue();
		} catch (Exception e) {
			Log.w(TAG, "Unable to decode " +  sCol + " into integer", e);
			return Color.BLACK;
		}
	}
	
	private List<ObjectGps> getGpsObjects(BObject object) {
		List<ObjectGps> ogList = mWeakMap.get(object);
		if (ogList == null) {
			Log.d(TAG, "could not find object: " + object +  "in cache, requerying");
			ogList = object.getGpsObjects();
			mWeakMap.put(object, ogList);
		}
		return ogList;
	}
}
